package example.fast;

import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.objectweb.asm.commons.GeneratorAdapter;

import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;

public class MethodFactory implements Opcodes {
    static Map<ClassLoader, MyClassloader> cacheClassloader = new HashMap<>();

    public static FastMethod create(Class bean, String methodName, Class returnClz, Class... args) throws Exception {
        return create(bean.getClassLoader(), bean.getName(), methodName, Type.getMethodDescriptor(Type.getType(returnClz), Arrays.stream(args).map(Type::getType).toArray(Type[]::new)));
    }

    public static FastMethod create(ClassLoader classLoader, String beanClass, String methodName, String desc) throws Exception {
        String fullClzName = getFastClzName(beanClass, methodName, desc);
        MyClassloader loader = cacheClassloader.get(classLoader);
        if (loader == null) {
            loader = new MyClassloader(classLoader);
            cacheClassloader.put(classLoader, loader);
        }
        if (!loader.isLoad(fullClzName)) {
            loader.defineClass(fullClzName, beanClass, methodName, desc);
        }
        Class<?> aClass = Class.forName(fullClzName, false, loader);

        return (FastMethod) aClass.getConstructor().newInstance();
    }

    public static FastMethod create(Method method) throws Exception {
        Class<?> declaringClass = method.getDeclaringClass();
        ClassLoader classLoader = declaringClass.getClassLoader();
        String methodName = method.getName();
        String beanClass = declaringClass.getName();
        return create(classLoader, beanClass, methodName, Type.getMethodDescriptor(method));
    }

    public static byte[] genClassBytes(String beanClass, String methodName, String desc) throws Exception {
        String className = getFastClzName(beanClass, methodName, desc);

        ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_MAXS);
        MethodVisitor methodVisitor;

        classWriter.visit(V1_8, ACC_PUBLIC | ACC_SUPER, getInternalName(className), null, "java/lang/Object", new String[]{getInternalName(FastMethod.class.getName())});
        {
            methodVisitor = classWriter.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
            methodVisitor.visitCode();
            methodVisitor.visitVarInsn(ALOAD, 0);
            methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
            methodVisitor.visitInsn(RETURN);
            methodVisitor.visitMaxs(1, 1);
            methodVisitor.visitEnd();
        }
        {
            methodVisitor = classWriter.visitMethod(ACC_PUBLIC | ACC_VARARGS, "invoke", "(Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;", null, null);
            methodVisitor.visitCode();
            methodVisitor.visitVarInsn(ALOAD, 1);
            methodVisitor.visitTypeInsn(CHECKCAST, getInternalName(beanClass));
            // 处理传参
            GeneratorAdapter generatorAdapter = new GeneratorAdapter(methodVisitor, ACC_PUBLIC, "", desc);
            for (int i = 0; i < Type.getArgumentTypes(desc).length; i++) {
                methodVisitor.visitVarInsn(ALOAD, 2);
                methodVisitor.visitIntInsn(BIPUSH, i);
                methodVisitor.visitInsn(AALOAD);
                generatorAdapter.unbox(Type.getArgumentTypes(desc)[i]);
            }
            // 执行方法
            methodVisitor.visitMethodInsn(INVOKEVIRTUAL, getInternalName(beanClass), methodName, desc, false);
            if (Type.getReturnType(desc).getSort() == Type.VOID) {   // 无返回值
                methodVisitor.visitInsn(ACONST_NULL);
            } else {
                generatorAdapter.box(Type.getReturnType(desc));
            }
            methodVisitor.visitInsn(ARETURN);
            methodVisitor.visitMaxs(-1, -1);
            methodVisitor.visitEnd();
        }
        classWriter.visitEnd();
        return classWriter.toByteArray();
    }

    static String getInternalName(String className) {
        return className.replaceAll("\\.", "/");
    }

    /**
     * 转换逻辑不可逆，class name能用的特殊字符只有两个 $ _
     */
    static String getFastClzName(String className, String method, String desc) {
        int i = desc.indexOf(")");
        String substring = desc.substring(1, i); //只留下()里的字符
        String descStr = substring.replaceAll(";", "_").replaceAll("/", "_");
        return className + "$" + method+"$"+descStr;
    }
}
